package model;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import model.FTRS.Index;
import model.arbolBSharp.ArbolBSharp;
import model.arbolBSharp.ArbolBSharpException;
import model.archivo.ArchivoBloques;
import model.archivo.ArchivoDiccionario;
import model.archivo.ArchivoRegistrosVariables;
import model.archivo.ArchivoTexto;
import model.bloque.Bloque;
import model.extras.CaseFolding;
import model.extras.FileExists;
import model.extras.ParserLinea;
import model.extras.StopWords;
import model.extras.bitArray.BitArrayException;
import model.registro.RegistroAudio;
import model.registro.RegistroDocumentosDisponibles;
import model.registro.RegistroFTRS;
import model.registro.RegistroDocumentos;
import model.trie.*;

public class DocumentsManager {

	private ArchivoTexto texto;
	private ArchivoRegistrosVariables archivoDocumentos;
	private String fullText;
	private ParserLinea parser = new ParserLinea(" "+","+"."+";"+":"+"-"+"("+")"+"\""+"{"+"}"+"'"+"["+"]"+"\n"+"\n\r");
	private ArchivoRegistrosVariables archivoAudio;
	private Trie aTrieDiccionario;
	private ArchivoDiccionario archivoDocsDisponibles;
	private ArbolBSharp arbol;
	private ArchivoBloques archOffsets;

	private int cantDocumentos=0;
	
	public DocumentsManager() throws FileNotFoundException, IOException, ArbolBSharpException
	{
		archivoDocumentos = new ArchivoRegistrosVariables("sistema/ArchivoDocumentos.dat");
		archivoDocsDisponibles = new ArchivoDiccionario("sistema/DocsDisponibles.dat");
		archivoDocumentos.ffArchivo();
		archivoAudio = new ArchivoRegistrosVariables("sistema/ArchivoAudio.dat");
		archivoAudio.ffArchivo();
		aTrieDiccionario = new Trie();
		this.archOffsets =  new ArchivoBloques("sistema/ArchivoListasDocs.dat",512,false);
		arbol = new ArbolBSharp(512);
		arbol.abrir("sistema/Arbol.dat");
		
		cantDocumentos = GetLoadedDocuments().size();
	}
	
	//Abre un archivo, dejandolo disponible para el resto de los metodos de la clase
	//Ademas lo guarda en el archivoDocumentos
	public int CargarTextoDocumento(String path,boolean grabarAudio) throws InterruptedException, IOException{
		texto = new ArchivoTexto(path);

		if (texto.getFilePointer()==texto.getOffSetEOF()){
			texto.cerrarArchivo();
			return -1;
		}

		if (grabarAudio==false) System.out.println("INFORMACION: Leyendo documento. Por favor aguarde...");
		fullText = texto.getTodo();
		
		//Se guardan las palabras en el trie y se graba su audio
		SaveAudioAndPutInTrie(fullText,grabarAudio);
		
		texto.cerrarArchivo();
		return 0;
	}
	
	public void ComprimirDocumento(String path, int algoritmo) throws Exception{	
		
		int tamanioTextoSinComprimir=fullText.getBytes("UTF-16BE").length;
		byte[] bytesTextoSinComprimir=new byte[tamanioTextoSinComprimir];
		bytesTextoSinComprimir=fullText.getBytes("UTF-16BE");
		
		
		int tamanioTextoComprimido=0;
		byte[] bytesTextoComprimido=null;
		
		switch (algoritmo) { 
		case 1:
				System.out.println("INFORMACION: Comprimiendo documento. Algoritmo: artimético. Por favor aguarde...");
				bytesTextoComprimido=CompressionManager.ComprimirAritmetico(bytesTextoSinComprimir);
				break;
		case 2:
				System.out.println("INFORMACION: Comprimiendo documento. Algoritmo: LZP. Por favor aguarde...");
				bytesTextoComprimido=CompressionManager.ComprimirLZP(bytesTextoSinComprimir);
				break;
		case 3:
				System.out.println("INFORMACION: Comprimiendo documento. Algoritmo: PPMC. Por favor aguarde...");
				bytesTextoComprimido=CompressionManager.ComprimirPPMC(bytesTextoSinComprimir);
				break;
		default:
				bytesTextoComprimido=bytesTextoSinComprimir;
				break;
		}
		
		RegistroAudio registroTexto = new RegistroAudio(bytesTextoComprimido);		
		tamanioTextoComprimido=bytesTextoComprimido.length;
		
		escribirRegistroArchDocsDisponibles(FileExists.CleanPath(path),(int)archivoDocumentos.getFilePointer(),algoritmo,(100-(tamanioTextoComprimido*100)/tamanioTextoSinComprimir));
		
		archivoDocumentos.escribirRegistro(registroTexto);		
	}
	
	public void CreateFTRSWorkFiles() throws Exception
	{
		ArrayList<RegistroFTRS> listaFtrs = new ArrayList<RegistroFTRS>();
		this.archOffsets =  new ArchivoBloques("sistema/ArchivoListasDocs.dat",512,true);
		Index index = new Index(this.archOffsets);
		int errorLVL=-1;
		errorLVL=index.LoadDocumentsFromDisc(archivoDocumentos,GetLoadedDocuments());
	
		if (errorLVL==0){
			listaFtrs = index.CreateDocsList();
			for (RegistroFTRS reg : listaFtrs) {
				arbol.insertar(reg.getPalabra(), reg.getOffSetDocumentos(), reg.getPesoGlobal());
			}			
		}
		
	}
	
	public ArrayList<RegistroDocumentosDisponibles> GetLoadedDocuments() throws IOException
	{
		ArrayList<RegistroDocumentosDisponibles> Temp = new ArrayList<RegistroDocumentosDisponibles>();
		
		archivoDocsDisponibles.rwArchivo();
		while (archivoDocsDisponibles.getFilePointer() < archivoDocsDisponibles.getOffSetEOF()) {
			RegistroDocumentosDisponibles regDocsIt = new RegistroDocumentosDisponibles();
			archivoDocsDisponibles.leerRegistro(regDocsIt);
			Temp.add(regDocsIt);
		}
		
		return Temp;
	}
	
	//Lee el archivo de texto en forma secuencial, parseando linea por linea del texto con el parser. 
	//Esta linea es guardada como palabras en un array de String que se ir leyendo por el AudioManager 
	//si es que la palabra se encuentra en el ArchivoDiccionario.
	public int ReadDocument(String path) throws Exception{

		if (archivoDocumentos.getOffSetEOF()==0)
			return -1;
		
		RegistroDocumentosDisponibles documentoALeer=null;
		//obtener el offset a partir del path
		
		ArrayList<RegistroDocumentosDisponibles> Temp = new ArrayList<RegistroDocumentosDisponibles>();
		Temp=GetLoadedDocuments();		
		for (int i=0; i<Temp.size();i++)
			if (Temp.get(i).getNombreDocumento().equals(path))
				documentoALeer=Temp.get(i);
		
		if (documentoALeer==null) {
			System.out.println("ERROR: El nombre ingresado no es correcto.\n");
			return -1;
		}
		
		ReadDocByoffset(documentoALeer);
		return 0;
	}

	private void ReadDocByoffset(RegistroDocumentosDisponibles xDocumentoALeer) throws Exception {
		AudioManager audio = new AudioManager();
		
		RegistroAudio registroTexto = new RegistroAudio();
		archivoDocumentos.leerRegistro(xDocumentoALeer.getOffSet(), registroTexto);
		
		byte[] bytesTextoSinComprimir = null;
		
		System.out.println("");
		switch (xDocumentoALeer.getAlgoritmoCompresion()) { 
		case 1:
				System.out.println("INFORMACION: Descomprimiendo documento. Algoritmo: artimético. Por favor aguarde...");
				bytesTextoSinComprimir=CompressionManager.DescomprimirAritmetico(registroTexto.convertirRegistroABytes());
				break;
		case 2:
				System.out.println("INFORMACION: Descomprimiendo documento. Algoritmo: LZP. Por favor aguarde...");
				bytesTextoSinComprimir=CompressionManager.DescomprimirLZP(registroTexto.convertirRegistroABytes());
				break;
		case 3:
				System.out.println("INFORMACION: Descomprimiendo documento. Algoritmo: PPMC. Por favor aguarde...");
				bytesTextoSinComprimir=CompressionManager.DescomprimirPPMC(registroTexto.convertirRegistroABytes());
				break;
		default:
				bytesTextoSinComprimir=registroTexto.convertirRegistroABytes();
				break;
		}
		
		String textoAleer = new String(bytesTextoSinComprimir, 0, bytesTextoSinComprimir.length, "UTF-16BE");
		
		System.out.println("Leyendo... Texto del documento seleccionado:");
		System.out.println(textoAleer);

		for (String word : parser.LineToArray(textoAleer)) {
			String tempWord = word.toLowerCase();
			int offsetAudio = aTrieDiccionario.ObtenerOffSetAudio(tempWord);
			if (offsetAudio>0){
				RegistroAudio regAux = new RegistroAudio();
				archivoAudio.leerRegistro(offsetAudio,regAux);
				audio.AudioPlay(regAux.getDatosAudio());
			}
		}
		System.out.println();
	}
	
	
	public void CloseFiles() throws IOException, ArbolBSharpException
	{
		archivoDocumentos.cerrarArchivo();
		archivoDocsDisponibles.cerrarArchivo();
		archivoAudio.cerrarArchivo();
		aTrieDiccionario.finalizar();
		archOffsets.cerrarArchivo();
		arbol.cerrar();		
	}	
	
	private void SaveAudioAndPutInTrie(String fullText,boolean grabarAudio) throws InterruptedException, IOException
	{
		for (String word : parser.LineToArray(fullText)) {
			String wordTemp = word.toLowerCase();
			if (!exists(wordTemp)) {
				AddWord(word,grabarAudio);
			}
		}		
	}

	//Se verifica si la palabra existe en el trie
	public boolean exists(String xpalabra) throws IOException{
		
		if (aTrieDiccionario.ObtenerOffSetAudio(xpalabra)<=0)
			return false;
		else
			return true;
	}
	
	public void AddWord(String word,boolean grabarAudio) throws InterruptedException, IOException{
		
		word = word.toLowerCase();	
		if (archivoAudio.getFilePointer()==0) {
			byte [] bytesPlaceholderAudio = new byte[]{0};
			RegistroAudio placeholderAudio = new RegistroAudio(bytesPlaceholderAudio);
			archivoAudio.escribirRegistro(placeholderAudio);
		}		
		if (grabarAudio==true) {		
			System.out.println("La Palabra a grabar es: \"" + word + "\"");
			AudioManager audio = new AudioManager();
			audio.AudioRecord();
			ByteArrayOutputStream byteArrayAudio = (ByteArrayOutputStream) audio.getRec().getOutput();			
			RegistroAudio regAudio = new RegistroAudio(byteArrayAudio.toByteArray());
			aTrieDiccionario.GuardarPalabra(word, (int)archivoAudio.getFilePointer());
			archivoAudio.escribirRegistro(regAudio);
		}
		else {
			aTrieDiccionario.GuardarPalabra(word,0);
		}
	}
	
	private void escribirRegistroArchDocsDisponibles(String word, int offsetDocList,int algoritmo,float grado) throws IOException
	{
		RegistroDocumentosDisponibles regDocsDispToAdd = new RegistroDocumentosDisponibles(word,offsetDocList,algoritmo,grado);
		archivoDocsDisponibles.escribirRegistro(regDocsDispToAdd);
		cantDocumentos++;
	}

	public void resolverConsulta(String consulta) throws Exception {
		ArrayList<String> arrayPalabras = new ArrayList<String>();
		ArrayList<RegistroFTRS> listaRegistro = new ArrayList<RegistroFTRS>();
		
		Integer[] docALeer = new Integer[5];
		int k=0;
		Bloque bloqueAux = new Bloque(512);
		this.fullText = CaseFolding.ApplyCaseFolding(consulta);
		
		//Se remueven todas las palabras que no se indexaran
		StopWords stopWords = new StopWords();
		ArrayList<String> noWords = stopWords.getNoGuardar();
		
		for (String word : parser.LineToArray(fullText)) {
			if (!arrayPalabras.contains(word) && (!noWords.contains(word)))
				arrayPalabras.add(word);
		}
		
		RegistroFTRS reg= new RegistroFTRS();
		//System.out.println("Las palabras a buscar son: ");
		for (String word : arrayPalabras) {
			reg = arbol.buscarPalabra(word);
			if (reg != null)
				listaRegistro.add(reg);
			else System.out.println("INFORMACION: No se encontraron documentos asociados a la palabra: \""+ word +"\".");
			//System.out.println(reg.getPalabra()+" pesoglobal:"+ reg.getPesoGlobal());
		}  
		if (listaRegistro.isEmpty()){
			System.out.println("INFORMACION: Su búsqueda no arrojó ningún resultado.\n");
		}else{
			System.out.println();
			RegistroFTRS[] listaAux = new RegistroFTRS[listaRegistro.size()];
			int it = 0;
			for (RegistroFTRS regAux : listaRegistro) {
				listaAux[it] = regAux;
				it++;
			}
			sortArray(listaAux);
			RegistroDocumentos regdocs= new RegistroDocumentos();
			byte[] byteArray = new byte[512];
			byte[] bytesReg = null;
			for (int i = 0; i < listaAux.length; i++) {
				this.archOffsets.LeerBloque(byteArray, listaAux[i].getOffSetDocumentos());
				bloqueAux.convertirBytesABloque(byteArray);
				bloqueAux.rwBloque();
				boolean encontrado = false;
				while (!encontrado) {
					regdocs = new RegistroDocumentos();
					bytesReg = bloqueAux.getProximoRegistro();
					//System.out.println(bytesReg.length);
					regdocs.convertirBytesARegistro(bytesReg);
					String tempPalabra = new String(regdocs.getPalabra());
					if (tempPalabra.equals(listaAux[i].getPalabra()))
							encontrado=true;
				}
				int j=0;
				
				boolean yaExiste=false;
				while ((j<regdocs.getOffDocs().size())&&(k<5)){
					
					yaExiste=false;
					for (int l=0; l<k; l++)
						if (docALeer[l].intValue()==regdocs.getOffDocs().get(j))
							yaExiste=true;
					
					if (!yaExiste) {
						docALeer[k] = regdocs.getOffDocs().get(j);					
						k++;
					}
					
					j++;
				}
											
			}
			
			RegistroDocumentosDisponibles regDocsIt = new RegistroDocumentosDisponibles();
            System.out.println("Documentos que contienen los términos buscados:");
            
            for(int i=0; i<(docALeer.length); i++) {
            	
            	if (docALeer[i] != null) {
            		archivoDocsDisponibles.rwArchivo();
            		while (archivoDocsDisponibles.getFilePointer() < archivoDocsDisponibles.getOffSetEOF()) {
            			archivoDocsDisponibles.leerRegistro(regDocsIt);
            			if (regDocsIt.getOffSet() == docALeer[i].intValue()) {
            				System.out.println(regDocsIt.getNombreDocumento());
            				break;
            			}
            		}
            	}
            	  	
            }
                       
            System.out.print("Ingrese el nombre de uno de los documentos disponibles para reproducir: ");
            BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
            String option = input.readLine();
            
            ReadDocument(option);
 		}
	}
	
	
	private void sortArray(RegistroFTRS[] listaAux) {
		for (int i = 1; i < listaAux.length; i++) {
			for (int j = 0; j < listaAux.length - 1; j++) {
				if (listaAux[j].getPesoGlobal() < listaAux[j + 1]
						.getPesoGlobal()) {
					RegistroFTRS temp = new RegistroFTRS();
					temp = listaAux[j];
					listaAux[j] = listaAux[j + 1];
					listaAux[j + 1] = temp;
				}
			}
		}
	}	
	
	public boolean indiceCreado() throws ArbolBSharpException {
		
		return (!arbol.isEmpty());		
		
	}
	
	public int GetCantDocumentos() {
		
		return cantDocumentos;	
		
	}
		
}
